package com.yanqu.road.ns0;

import java.util.List;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrameDecoder;

public class YanQuWSDecipher0 extends ReplayingDecoder<Void> implements WebSocketFrameDecoder {
	static final int DEFAULT_MAX_FRAME_SIZE = 16384;

	private final long maxFrameSize;
	private boolean receivedClosingHandshake;

	public YanQuWSDecipher0() {
		this(DEFAULT_MAX_FRAME_SIZE);
	}

	/**
	 * Creates a new instance of {@code WebSocketFrameDecoder} with the
	 * specified {@code maxFrameSize}. If the client sends a frame size larger
	 * than {@code maxFrameSize}, the channel will be closed.
	 * 
	 * @param maxFrameSize
	 *            the maximum frame size to decode
	 */
	public YanQuWSDecipher0(int maxFrameSize) {
		this.maxFrameSize = maxFrameSize;
	}

	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
		// Discard all data received if closing handshake was received before.
		if (receivedClosingHandshake) {
			in.skipBytes(actualReadableBytes());
			return;
		}

		// Decode a frame otherwise.
		byte type = in.readByte();
		WebSocketFrame frame;
		if ((type & 0x80) == 0x80) {
			// If the MSB on type is set, decode the frame length
			frame = decodeBinaryFrame(ctx, type, in);
		} else {
			// Decode a 0xff terminated UTF-8 string
			frame = decodeTextFrame(ctx, in);
		}

		if (frame != null) {
			out.add(frame);
		}
	}

	private WebSocketFrame decodeBinaryFrame(ChannelHandlerContext ctx, byte type, ByteBuf buffer) {
		long frameSize = 0;
		int lengthFieldSize = 0;
		byte b;
		do {
			b = buffer.readByte();
			frameSize <<= 7;
			frameSize |= b & 0x7f;
			if (frameSize > maxFrameSize) {
				throw new TooLongFrameException();
			}
			lengthFieldSize++;
			if (lengthFieldSize > 8) {
				// Perhaps a malicious peer?
				throw new TooLongFrameException();
			}
		} while ((b & 0x80) == 0x80);

		if (type == (byte) 0xFF && frameSize == 0) {
			receivedClosingHandshake = true;
			return new CloseWebSocketFrame();
		}
		ByteBuf payload = ctx.alloc().buffer((int) frameSize);
		buffer.readBytes(payload);
		return new BinaryWebSocketFrame(payload);
	}

	private WebSocketFrame decodeTextFrame(ChannelHandlerContext ctx, ByteBuf buffer) {
		int ridx = buffer.readerIndex();
		int rbytes = actualReadableBytes();
		int delimPos = buffer.indexOf(ridx, ridx + rbytes, (byte) 0xFF);
		if (delimPos == -1) {
			// Frame delimiter (0xFF) not found
			if (rbytes > maxFrameSize) {
				// Frame length exceeded the maximum
				throw new TooLongFrameException();
			} else {
				// Wait until more data is received
				return null;
			}
		}

		int frameSize = delimPos - ridx;
		if (frameSize > maxFrameSize) {
			throw new TooLongFrameException();
		}

		ByteBuf binaryData = ctx.alloc().buffer(frameSize);
		buffer.readBytes(binaryData);
		buffer.skipBytes(1);

		int ffDelimPos = binaryData.indexOf(binaryData.readerIndex(), binaryData.writerIndex(), (byte) 0xFF);
		if (ffDelimPos >= 0) {
			throw new IllegalArgumentException("a text frame should not contain 0xFF.");
		}

		return new TextWebSocketFrame(binaryData);
	}

}
